﻿
Imports System.IO

Public Module _process
    Private proc As Process = Nothing
    Public Function currentProcess() As Process
        If proc Is Nothing Then
            atomic.eva(proc, Process.GetCurrentProcess())
        End If

        Return proc
    End Function

#If Not (PocketPC OrElse Smartphone) Then
    Public Function findapplication(ByVal appname As String) As Process()
        appname = filename(appname)
        Return Process.GetProcessesByName(appname)
    End Function

    Public Function existingInstance(Optional ByVal twoInstancesAsExpected As Boolean = True) As Boolean
        Dim procs() As Process = Nothing
        procs = findapplication(Process.GetCurrentProcess.ProcessName())

        If twoInstancesAsExpected Then
            assert(procs.Length() <= 2, "already have more then two instances?")
        End If

        Return procs.Length = 2
    End Function

    Public Function existingInstance(ByVal appname As String) As Boolean
        Dim procs() As Process = Nothing
        procs = findapplication(appname)

        Return procs.Length >= 1
    End Function

    Public Sub keepOneInstance()
        If existingInstance() Then
            forceQuit(0)
        End If
    End Sub
#End If

    Public Sub forceQuit(Optional ByVal extCode As Int32 = -1)
        Windows.Forms.Application.Exit()
#If Not (PocketPC OrElse Smartphone) Then
        Environment.Exit(extCode)
        Environment.FailFast(Convert.ToString(extCode))
#End If
        closeProcess(Process.GetCurrentProcess(), , False)
    End Sub

    Public Const defaultCloseProcessWaittime As Int32 = 30000

    Public Class closeProcessInfo
        Public proc As Process = Nothing
        Public waittime As Int32 = defaultCloseProcessWaittime
    End Class

    Private Sub closeProcessImpl(ByVal obj As Object)
        On Error Resume Next

        Dim info As closeProcessInfo = Nothing
        info = convertor.cast(Of closeProcessInfo)(obj)
        If Not info Is Nothing Then
            Dim waittime As Int32 = defaultCloseProcessWaittime
            Dim proc As Process = Nothing
            waittime = info.waittime
            proc = info.proc

            If waittime < 0 Then
                raiseError("cannot wait for a navigate time, " + Convert.ToString(waittime) _
                           + ", use default value.", errorHandle.errorType.warning)
                waittime = defaultCloseProcessWaittime
            End If
            If Not proc Is Nothing Then
                If Not processExited(proc) Then
                    If Not proc.CloseMainWindow() Then
                        raiseError("failed to send CloseMainWindow message to process, use SendMessage instead.")
                        SendMessage(proc.MainWindowHandle(), windowsMessage.WM_CLOSE, 0, 0)
                    End If
                    proc.WaitForExit(waittime)
                    If Not processExited(proc) Then
                        raiseError("process " + proc.StartInfo.FileName + " cannot be close within " _
                                    + Convert.ToString(waittime) + "ms, force kill.")
                        proc.Kill()
                    End If
                End If

                proc.Close()
            End If
        Else
            raiseError(errorHandle.errorType.exclamation, "no input closeProcessInfo")
        End If
    End Sub

    Public Sub closeProcess(ByRef proc As Process, Optional ByVal waittime As Int32 = defaultCloseProcessWaittime _
                            , Optional ByVal async As Boolean = True)
        Dim info As closeProcessInfo = Nothing
        info = New closeProcessInfo()
        info.proc = proc
        info.waittime = waittime
        If async Then
            Dim thread As Threading.Thread = Nothing
            thread = startThread(AddressOf closeProcessImpl, info)
        Else
            closeProcessImpl(info)
        End If
    End Sub

    Public Function startprocess(ByRef proc As Process, _
                                 ByVal beforestart As _do(Of Process, Boolean)) As Boolean
        assert(Not beforestart Is Nothing)
        If proc Is Nothing Then
            proc = New Process()
        Else
            closeProcess(proc, , False)
        End If
        If Not beforestart(proc) Then
            raiseError(errorHandle.errorType.exclamation, "cannot finish beforestart.")
            Return False
        End If
        Dim cur As String = Nothing
        cur = Environment.CurrentDirectory
        Environment.CurrentDirectory = proc.StartInfo.WorkingDirectory
        Dim rtn As Boolean = False
        Try
            rtn = proc.Start()
        Catch ex As Exception
            raiseError("cannot start process " + proc.StartInfo.FileName + _
                       ", with argument " + proc.StartInfo.Arguments + ", ex " + ex.Message, _
                       errorHandle.errorType.exclamation)
            closeProcess(proc)
            proc = Nothing
            rtn = False
        End Try
        Environment.CurrentDirectory = cur
        Return rtn
    End Function

    Public Function startprocess(ByRef proc As Process, ByVal app As String, ByVal argument As String, _
                                 ByVal beforestart As _do(Of Process, Boolean), _
                                 ByVal ParamArray envs() As String) As Boolean
        Dim beforestart2 As _do(Of Process, Boolean) = _
            Function(ByRef p As Process) As Boolean
#If RETIRED Then
                If Not Path.IsPathRooted(app) Then
                    p.StartInfo.FileName = Path.GetFullPath(app)
                Else
                    copy(p.StartInfo.FileName, app)
                End If
#End If
                copy(p.StartInfo.FileName, app)
                copy(p.StartInfo.Arguments, argument)
                p.StartInfo.WorkingDirectory = getparentpath(app)
                If Not envs Is Nothing AndAlso envs.Length > 1 Then
                    p.StartInfo.UseShellExecute = False
                    Dim i As Int64
                    For i = 0 To envs.Length() - 1 Step 2
                        p.StartInfo.EnvironmentVariables(envs(i)) = envs(i + 1)
                    Next
                    If i <> envs.Length() Then
                        raiseError("envs for startprocess is not paired.", errorHandle.errorType.warning)
                    End If
                End If
                If beforestart Is Nothing Then
                    Return True
                Else
                    Return beforestart(p)
                End If
            End Function
        Return startprocess(proc, beforestart2)
    End Function

    Public Function startprocess(ByRef proc As Process, ByVal app As String, ByVal argument As String, _
                                 ByVal ParamArray envs() As String) As Boolean
        Return startprocess(proc, app, argument, Nothing, envs)
    End Function

    Private Function processInfo(Of T)(ByVal p As Process,
                                       ByVal d As _do(Of T),
                                       ByRef o As T) As Boolean
        assert(Not d Is Nothing)
        If p Is Nothing Then
            Return False
        Else
            Try
                o = d()
                Return True
            Catch
                Return False
            End Try
        End If
    End Function

    Private Delegate Function getprocessInfo(Of T)(ByVal p As Process, ByRef o As T) As Boolean
    Private Function processInfo(Of T)(ByVal p As Process,
                                       ByVal getinfo As getprocessInfo(Of T),
                                       ByVal failedValue As T) As T
        assert(Not getinfo Is Nothing)
        Dim o As T = Nothing
        If getinfo(p, o) Then
            Return o
        Else
            Return failedValue
        End If
    End Function

    Public Function processExited(ByVal p As Process, ByRef e As Boolean) As Boolean
        Return processInfo(p, Function() p.HasExited(), e)
    End Function

    Public Function processExited(ByVal p As Process) As Boolean
        Return processInfo(p, AddressOf processExited, True)
    End Function

    Public Function processExitTime(ByVal p As Process, ByRef t As Int64) As Boolean
        Return processInfo(p, Function() p.ExitTime().Ticks(), t)
    End Function

    Public Function processExitTime(ByVal p As Process) As Int64
        Return processInfo(p, AddressOf processExitTime, npos)
    End Function

    Public Function processExitCode(ByVal p As Process, ByRef e As Int32) As Boolean
        Return processInfo(p, Function() p.ExitCode(), e)
    End Function

    Public Function processExitCode(ByVal p As Process) As Int32
        Return processInfo(p, AddressOf processExitCode, npos)
    End Function

    Public Function processFilename(ByVal p As Process, ByRef f As String) As Boolean
        Return processInfo(p, Function() p.StartInfo().FileName(), f)
    End Function

    Public Function processFilename(ByVal p As Process) As String
        Return processInfo(p, AddressOf processFilename, "#UNKNOWN#")
    End Function

    Public Function processId(ByVal p As Process, ByRef id As Int32) As Boolean
        Return processInfo(p, Function() p.Id(), id)
    End Function

    Public Function processId(ByVal p As Process) As Int32
        Return processInfo(p, AddressOf processId, npos)
    End Function

#If Not (PocketPC OrElse Smartphone) Then
    Public Function processPriority(ByVal pri As String) As ProcessPriorityClass
        If strsame(pri, "idle", False) Then
            Return ProcessPriorityClass.Idle
        ElseIf strsame(pri, "belownormal", False) Then
            Return ProcessPriorityClass.BelowNormal
        ElseIf strsame(pri, "abovenormal", False) Then
            Return ProcessPriorityClass.AboveNormal
        ElseIf strsame(pri, "high", False) Then
            Return ProcessPriorityClass.High
        ElseIf strsame(pri, "realtime", False) Then
            Return ProcessPriorityClass.RealTime
        Else
            Return ProcessPriorityClass.Normal
        End If
    End Function
#End If

End Module
